<!-- 多小球碰撞检测，鼠标点击有气泡效果 -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>多小球碰撞检测，鼠标点击有气泡效果</title>
    <style>
        canvas {
            background-color: black;
        }
    </style>
</head>

<body>
    <canvas id="canvas"></canvas>
</body>
<script src="../utils.js"></script>
<script>
    // 小球绘制
    class Ball {
        constructor(options) {
            this.x = 0;
            this.y = 0;
            this.r = 200;
            this.fillStyle = 'blue';
            this.strokeStyle = 'black';
            // 小球缩放
            this.scaleX = 1;
            this.scaleY = 1;
            // 定义小球移动速度
            this.vx = 0;
            this.vy = 0;
            // 小球透明度
            this.alpha = 1;
            Object.assign(this, options);
            return this;
        }
        render(ctx) {
            let { x, y, r, fillStyle, strokeStyle, scaleX, scaleY, alpha } = this;
            // 保存绘图环境
            ctx.save();
            // 开始绘制
            // ctx.beginPath();
            ctx.beginPath();
            // ctx.translate(x / 2, y / 2);
            ctx.scale(scaleX, scaleY);
            ctx.strokeStyle = strokeStyle;
            ctx.fillStyle = fillStyle;

            ctx.globalAlpha = alpha;

            ctx.arc(x, y, r, 0, Math.PI * 2);


            ctx.fill();
            ctx.stroke();
            ctx.restore();
            ctx.closePath();
            return this;
        }
    }
    let canvas = document.getElementById('canvas');
    let ctx = canvas.getContext('2d');
    let w = canvas.width = 1000;
    let h = canvas.height = 1000;
    let firstBallR = 100, bigBall = false;

    let mouse = c.getOffset(canvas);

    // 存放所有小球实例
    let balls = [];
    // 定义小球实例数
    let number = 100;

    for (let i = 0; i < number; i++) {
        balls[i] = new Ball({
            x: c.rand([50, w - 50]),
            y: c.rand([50, h - 50]),
            r: c.rand([10, 50]),
            fillStyle: `rgb(${c.rand([50, 255])},${c.rand([50, 255])},${c.rand([50, 255])})`,
            strokeStyle: 'black',
            scaleX: 1,
            scaleY: 1,
            alpha: 1,
            vx: c.rand([-3, 3]),
            vy: c.rand([-3, 3]),
        });
    }

    // 摩擦力
    let friction = -0.8;
    // 重力
    let g = 0.002;
    // 缓动系数
    let spring = 0.03;


    /**
     * 检测小球是否碰撞边界
     * @param {Ball} ball 小球实例
     * @returns {boolean} true:碰撞,false:不碰撞
     */
    function checkBounce(ball) {
        if (ball.x - ball.r < 0 || ball.x + ball.r > w || ball.y - ball.r < 0 || ball.y + ball.r > h) {
            return true;
        }
        return false;
    }

    /**
     * 检测小球是否碰撞
     * @param {Ball} ball1 小球实例
     * @param {Ball} ball2 小球实例
     * @returns {boolean} true:碰撞,false:不碰撞
     */
    function checkHit(ball1, ball2) {
        // 如果两个小球圆心的位置小于两个半径，说明两个小球碰撞了
        return Math.sqrt((ball1.x - ball2.x) ** 2 + (ball1.y - ball2.y) ** 2) < ball1.r + ball2.r;
    }


    // 增加一个ball，不进行渲染
    let b = new Ball({
        r: 200,
        x: w / 2,
        y: h / 2
    });
    balls[0] = b;
    // 气泡跟随鼠标移动
    canvas.addEventListener('mousemove', function (e) {
        balls[0].x = mouse.x;
        balls[0].y = mouse.y;
    });

    // 点击画布时，小球扩散
    canvas.addEventListener('click', function (e) {
        firstBallR = firstBallR < h / 2 ? firstBallR + 50 : 100;
        balls[0].r = firstBallR;
    });


    // 鼠标移出画布，气泡效果消失
    canvas.addEventListener('mouseover', function (e) {
        bigBall = true;
    });
    canvas.addEventListener('mouseout', function (e) {
        bigBall = false;
    });
    (function move() {
        window.requestAnimationFrame(move);
        ctx.clearRect(0, 0, w, h);
        balls.forEach((item, i) => {
            // 移除气泡效果
            if (!i && !bigBall) return;
            // item.vy += g;
            for (let j = i + 1; j < number; j++) {

                const ballB = balls[j];
                const dx = ballB.x - item.x, dy = ballB.y - item.y;
                const dist = Math.sqrt((ballB.x - item.x) ** 2 + (ballB.y - item.y) ** 2);
                const minDist = item.r + ballB.r;
                if (dist < minDist) {
                    // 目标点
                    const tx = item.x + dx / dist * minDist;
                    const ty = item.y + dy / dist * minDist;
                    // 缓动加速度
                    const ax = (tx - ballB.x) * spring;
                    const ay = (ty - ballB.y) * spring;
                    item.vx -= ax;
                    item.vy -= ay;
                    ballB.vx += ax;
                    ballB.vy += ay;
                }
            }
            // 碰撞检测
            if (checkBounce(item)) {
                item.vx *= friction;
                item.vy *= friction;
            }
            item.y += item.vy;
            item.x += item.vx;
            // balls_copy = balls.slice(index + 1, 1);
            if (i != 0) {
                item.render(ctx);
            }

        });


    })()
</script>

</html>